package com.world.common.filter;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import com.alibaba.fastjson2.JSONObject;
import com.world.common.annotation.AnonymousAccess;
import com.world.common.constants.PromptConstants;
import com.world.common.constants.RedisKeyConstants;
import com.world.common.constants.SystemConstants;
import com.world.common.domain.ErrorResponseData;
import com.world.common.domain.ResponseData;
import com.world.common.security.SecurityConstants;
import com.world.common.security.SecurityContextHolder;
import com.world.login.domain.entity.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 认证过滤器
 *
 * @author WangYX
 * @version 1.0.0
 * @date 2023/06/01 14:24
 */
@Slf4j
@Component
public class AuthenticationFilter extends OncePerRequestFilter {


    /**
     * 可匿名访问的请求
     */
    private volatile static Set<String> anonymousUrls;

    private final RedisTemplate<String, Long> redisTemplate = SpringUtil.getBean("redisTemplate");

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //双重检测锁模式保证线程安全
        if (anonymousUrls == null) {
            synchronized (AuthenticationFilter.class) {
                if (anonymousUrls == null) {
                    getAnonymousUrls();
                }
            }
        }

        String contextPath = request.getContextPath();
        String requestURI = request.getRequestURI().replaceFirst(contextPath, "");

        log.info("请求路径：{}", requestURI);

        //跳过不需要验证的路径
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (String anonymousUrl : anonymousUrls) {
            if (antPathMatcher.match(anonymousUrl, requestURI)) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        //判断TOKEN是否存在
        String authorization = request.getHeader(SystemConstants.AUTHORIZATION);
        if (StrUtil.isBlank(authorization)) {
            returnError(response, PromptConstants.TOKEN_IS_BLANK);
            return;
        }

        try {
            //验证TOKEN的合法性
            boolean verify = JWTUtil.verify(authorization, signer());
            if (!verify) {
                returnError(response, PromptConstants.TOKEN_IS_ERROR);
                return;
            }

            JWT jwt = JWTUtil.parseToken(authorization);

            //验证TOKEN的时间
            JWTValidator.of(jwt).validateDate(DateUtil.date());

            //当前登录用户存入当前线程变量中
            LoginUser loginUser = convertToLoginUser(jwt);
            SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);

            //验证是否已经退出
            Boolean b = redisTemplate.hasKey(RedisKeyConstants.LOGIN_KEY + loginUser.getId());
            if (!b) {
                returnError(response, HttpStatus.HTTP_UNAUTHORIZED, PromptConstants.TOKEN_IS_EXPIRE);
                return;
            }
        } catch (ValidateException e) {
            returnError(response, PromptConstants.TOKEN_IS_EXPIRE);
            return;
        } catch (Exception e) {
            returnError(response, PromptConstants.TOKEN_IS_ERROR);
            return;
        }

        filterChain.doFilter(request, response);
    }

    /**
     * jwt转换为LoginUser
     *
     * @param jwt jwt
     * @return {@link LoginUser}
     * @author WangYX
     * @date 2023/06/02 13:53
     */
    private LoginUser convertToLoginUser(JWT jwt) {
        cn.hutool.json.JSONObject payloads = jwt.getPayloads();
        Long id = payloads.getLong("id");
        String name = payloads.getStr("name");
        String loginName = payloads.getStr("loginName");
        return LoginUser.builder().id(id).name(name).loginName(loginName).build();
    }

    /**
     * 获取JWT的signer
     *
     * @return {@link JWTSigner}
     * @author WangYX
     * @date 2023/06/02 12:38
     */
    private JWTSigner signer() {
        return SpringUtil.getBean(JWTSigner.class);
    }

    /**
     * 获取可匿名访问的请求路径
     *
     * @author WangYX
     * @date 2023/06/02 10:31
     */
    private void getAnonymousUrls() {
        ApplicationContext applicationContext = SpringUtil.getApplicationContext();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class).getHandlerMethods();

        anonymousUrls = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (anonymousAccess != null) {
                RequestMappingInfo key = infoEntry.getKey();
                PatternsRequestCondition patternsCondition = key.getPatternsCondition();
                if (patternsCondition != null) {
                    Set<String> patterns = patternsCondition.getPatterns();
                    anonymousUrls.addAll(patterns);
                }
            }
        }

        //swagger配置
        anonymousUrls.add("/webjars/**");
        anonymousUrls.add("/swagger-ui.html");
        anonymousUrls.add("/swagger-resources/**");
        anonymousUrls.add("/v2/api-docs");
        anonymousUrls.add("/druid/**");

        anonymousUrls.forEach(s -> log.warn("可以匿名访问的url：{}", s));
    }

    /**
     * 返回异常
     *
     * @param response HttpServletResponse
     * @param error    错误信息
     * @author WangYX
     * @date 2023/06/02 11:23
     */
    private void returnError(HttpServletResponse response, String error) {
        this.returnError(response, HttpStatus.HTTP_BAD_REQUEST, error);
    }

    /**
     * 返回异常
     *
     * @param response HttpServletResponse
     * @param code     错误码
     * @param error    错误信息
     * @author WangYX
     * @date 2023/10/07 14:29
     */
    @SuppressWarnings("rawtypes")
    private void returnError(HttpServletResponse response, Integer code, String error) {
        try {
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.setStatus(code);
            ResponseData fail = new ErrorResponseData(code, error);
            response.getWriter().write(JSONObject.toJSONString(fail));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}
